package org.cainiao.io.nio.channel;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;

public class ChannelCopy {

    /**
     * 这个代码从 stdin 复制数据到 stdout
     * 类似 'cat' 命令，但没有有用的选项可用
     */
    public static void main(String[] argv) throws IOException {
        try (ReadableByteChannel source = Channels.newChannel(System.in);
             WritableByteChannel dest = Channels.newChannel(System.out)) {
            channelCopy1(source, dest);
            // alternatively, call channelCopy2 (source, dest);
        }
    }

    /**
     * 复制通道方法 1
     * 这个方法从 src 通道复制数据并将其写入 dest 通道，直到在 src 中遇到 EOF
     * 这个实现利用临时缓冲区上的 compact() 方法，在数据没有完全排空时向下移动数据
     * 这会有复制数据的开销，但可以减少系统调用
     * It also requires a cleanup loop to make sure all the data gets sent.
     */
    private static void channelCopy1(ReadableByteChannel src, WritableByteChannel dest)
        throws IOException {
        ByteBuffer buffer = ByteBuffer.allocateDirect(16 * 1024);
        while (src.read(buffer) != -1) {
            // 让 buffer 为排出做好准备
            buffer.flip();
            // 写出到通道，可能会阻塞
            dest.write(buffer);
            // 如果只传输了部分数据，则向下移动剩下的数据
            // 如果 buffer 是空的，则等价于执行了 clear()
            buffer.compact();
        }
        // EOF will leave buffer in fill state
        buffer.flip();
        // Make sure that the buffer is fully drained
        while (buffer.hasRemaining()) {
            dest.write(buffer);
        }
    }

    /**
     * 复制通道方法 2
     * 这个方法完成相同的复制
     * 但它可以保证在读取更多数据前临时缓冲区是空的
     * 这样就不需要复制了，但会导致更多的系统调用
     * 不再需要后置的清理循环，因为缓冲区在第一个循环结束时已经空了
     */
    private static void channelCopy2(ReadableByteChannel src, WritableByteChannel dest)
        throws IOException {
        ByteBuffer buffer = ByteBuffer.allocateDirect(16 * 1024);
        while (src.read(buffer) != -1) {
            // Prepare the buffer to be drained
            buffer.flip();
            // Make sure that the buffer was fully drained
            while (buffer.hasRemaining()) {
                dest.write(buffer);
            }
            // Make the buffer empty, ready for filling
            buffer.clear();
        }
    }

}
